package org.infodavid.common.impl;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.IBuilder;
import org.infodavid.common.IObjectFactory;

/**
 * The Class ObjectFactoryImpl.
 * @param <T> the generic type
 */
@SuppressWarnings({
  "unchecked"
})
public class ObjectFactoryImpl<T extends Serializable> implements IObjectFactory<T> {

  /**
   * The implementation of the {@link IBuilder}.
   * @param <T> the generic type
   */
  public static class BuilderImpl<T extends Object> implements IBuilder<T> {
    /** The logger. */
    private static final Log LOG = LogFactory.getLog(BuilderImpl.class);

    /** The constructor. */
    private final Constructor<? extends T> constructor;

    /**
     * Constructor.
     * @param implementClass The concrete class associated to the T abstract type
     * @throws NoSuchMethodException The exception
     */
    public BuilderImpl(final Class<? extends T> implementClass) throws NoSuchMethodException {
      super();

      constructor = implementClass.getDeclaredConstructor();
      constructor.setAccessible(true);
    }

    /*
     * (non-Javadoc)
     * @see com.pdf.fs.entity.EntityFactory.IBuilder#create()
     */
    public T create() throws InstantiationException {
      try {
        return constructor.newInstance();
      }
      catch (final Exception e) {
        LOG.error("AN error occurs while instantiating object", e);

        throw new InstantiationException(e.getMessage());
      }
    }
  }

  /** Mappings. */
  private final Map<Class<? extends T>,IBuilder<? extends T>> mappings =
      new HashMap<Class<? extends T>,IBuilder<? extends T>>();

  /**
   * Constructor.
   * @throws NoSuchMethodException The exception
   */
  public ObjectFactoryImpl() throws NoSuchMethodException {
    super();
  }

  /**
   * Gets the mappings.
   * @return the mappings
   */
  public Map<Class<? extends T>,IBuilder<? extends T>> getMappings() {
    return mappings;
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.util.IObjectFactory#getEntityClass(java.io.Serializable)
   */
  @Override
  public <E extends T> Class<? extends T> getEntityClass(final E entity) {
    Class<? extends T> result = null;

    for (final Class<? extends T> item : getMappings().keySet()) {
      if (item.isInstance(entity)) {
        result = item;

        break;
      }
    }

    return result;
  };

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.util.IObjectFactory#create(java.lang.Class)
   */
  public <E extends T> E create(final Class<E> clz) throws InstantiationException {
    if (clz == null) {
      throw new InstantiationException("Given class is not valid");
    }

    final IBuilder<E> builder = getBuilder(clz);

    if (builder == null) {
      throw new InstantiationException("Given class is not valid: " + clz);
    }

    return builder.create();
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.util.IObjectFactory#getBuilder(java.lang.Class)
   */
  public <E extends T> IBuilder<E> getBuilder(final Class<E> clz) {
    return (IBuilder<E>)mappings.get(clz);
  }
}
